Skip to content

fix(mcp): auto-reconnect on transport errors (#25287)#25670

Open
water-in-stone wants to merge 1 commit intoanomalyco:devfrom
water-in-stone:fix/error-for-mcp-transport-error
Open

fix(mcp): auto-reconnect on transport errors (#25287)#25670
water-in-stone wants to merge 1 commit intoanomalyco:devfrom
water-in-stone:fix/error-for-mcp-transport-error

Conversation

@water-in-stone
Copy link
Copy Markdown

@water-in-stone water-in-stone commented May 4, 2026

Issue for this PR

Closes #25287

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Long-running MCP sessions break silently when the remote server restarts, hot-swaps its StreamableHTTP session, or the socket drops. The client keeps the stale transport and every subsequent tool call fails with an unhelpful error, even though a fresh connect would recover. Users had to restart the whole opencode session to unstick things.

  • Add an isTransportError() classifier that recognizes StreamableHTTPError (400/404/410/500 and SDK code -1) and raw socket failures (Bun ConnectionRefused, Node ECONNREFUSED/ECONNRESET, Undici UND_ERR_SOCKET, fetch-failed fallback), while leaving 401/403 auth flows and ordinary business errors untouched.
  • On a transport error during a tool call, trigger a single-flight reconnect (concurrent calls share one in-flight reconnect) and retry once with the fresh client from state. Non-transport errors and failed reconnects are rethrown as-is so real business errors stay visible.

How it was validated

  • Unit tests (test/mcp/transport-error.test.ts and test/mcp/mcp-reconnect.test.ts): 30+ cases covering every known error shape the classifier must accept or reject.

How did you verify your code works?

I created an MCP Server called 'xingtian' locally and sent a question — What features does the Xingtian editor have? — to simulate usage from a normal user. Then I manually restarted the xingtian MCP Server and sent another question: What is the latest version of the xingtian editor?. The second question failed with a Streamable HTTP error without my fix, but returned a correct response after applying my fix.

Before Fix

image

After Fix

image

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

The following comment was made by an LLM, it may be inaccurate:

Based on the search results, I found potentially related PRs:

  1. PR fix(opencode): reconnect MCP transport on session-expiration error and retry once #25135 - fix(opencode): reconnect MCP transport on session-expiration error and retry once

  2. PR fix(opencode): reconnect on network disruptions (VPN switch, SSE timeout, connection reset) #19116 - fix(opencode): reconnect on network disruptions (VPN switch, SSE timeout, connection reset)

These should be reviewed to ensure there's no conflicting implementation or duplicated effort, especially PR #25135 which appears to be directly addressing the same MCP reconnection problem.

@water-in-stone
Copy link
Copy Markdown
Author

water-in-stone commented May 4, 2026

Relationship to existing issues

Thank you for the pointer — this is indeed related but distinct from the existing issues:

Key distinction: two failure modes

Failure mode Request reaches server? Current behavior Fix location
Stale session ID (404) ✅ Yes SDK throws, no retry Could be server-side (strip header) OR client-side (reconnect)
Dead TCP connection (socket error) ❌ No SDK throws at transport level, no retry Must be client-side

@water-in-stone water-in-stone force-pushed the fix/error-for-mcp-transport-error branch from 5c803b8 to 631d74e Compare May 4, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP remote client has no transport-level retry on socket/connection errors

1 participant